// ========================================================================
// Copyright (c) 2022 ChangYou, All rights reserved.
// http://www.changyou.com/
// 
// Filename:    FoliageMeshInstance.cs
// Time:        2022-08-22 20:53:14
// Author:      xiangjinbao
// Email:       xiangjinbao@cyou-inc.com
// Version:     v2022.0.1
// Description: FoliageMeshInstance
// ========================================================================

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;

namespace ProjectTW.Foliage
{
    /// <summary>
    /// 细节对象模型实例。
    /// </summary>
    public class FoliageMeshInstance
    {
        public const int Generation_Vertices_Max = 65535;
        public const int Generation_Safe_Distance = 3;
        public const int Generation_Safe_Distance_Radius_Adjuster = 1;

        public static readonly Vector3 GenerationOptimizationPreGeneratedVector3Zero = Vector3.zero;
        public static readonly Vector3 GenerationOptimizationPreGeneratedVector3One = Vector3.one;
        public static readonly Quaternion GenerationOptimizationPreGeneratedQuaternionIdentity = Quaternion.identity;
        public static readonly Matrix4x4 GenerationOptimizationPreGenerationMatrixIdentity = Matrix4x4.identity;

        private static readonly List<UNCombineInstance> ReusableCombineInstances = new List<UNCombineInstance>();

        public FoliagePrototype Prototype;

        public Vector3 Position;
        public Vector3 Center;

        [System.NonSerialized]
        public Bounds CullBounds;
        [System.NonSerialized]
        public int BoundsSizeX;
        [System.NonSerialized]
        public int BoundsSizeZ;
        [System.NonSerialized]
        public Vector3 BoundsExtents;
        [System.NonSerialized]
        public int MeshIndex;
        
        [System.NonSerialized]
        private float m_DistanceToCenter;
        
        public Vector3 GetPosition(Vector3 pos)
        {
            return pos + Position;
        }

        public float DistanceToCenter
        {
            get
            {
                return m_DistanceToCenter;
            }
            private set
            {
                m_DistanceToCenter = value;
            }
        }

        /// <summary>
        /// Create a new foliage mesh.
        /// </summary>
        /// <param name="prototype"></param>
        /// <param name="position"></param>
        /// <param name="latestCreatedMesh"></param>
        /// <returns></returns>
        internal static FoliageMeshInstance CreateFoliageMeshInstance(FoliagePrototype prototype, Vector3 position, int meshIndex)
        {
            FoliageMeshInstance instance = new FoliageMeshInstance();

            instance.Prototype = prototype;
            instance.Position = position;
            instance.MeshIndex = meshIndex;

            return instance;
        }

        /// <summary>
        /// Generates the mesh instances
        /// </summary>
        /// <param name="prototypeIndex"></param>
        /// <param name="densities"></param>
        /// <param name="meshGroup"></param>
        /// <param name="resolution"></param>
        /// <returns>
        /// 1RD Dimension -> mesh lods index
        /// 2RD Dimension -> density lods index
        /// 3RD Dimension -> Mesh index
        /// </returns>
        public static Mesh[,,] CreateFoliageInstances(int prototypeIndex, List<byte> densities, out FoliageMeshInstancesGroup meshGroup, FoliageResolutions resolution)
        {
            meshGroup = new FoliageMeshInstancesGroup();

            FoliagePrototype prototype = FoliageDB.sortedPrototypes[prototypeIndex];
            float resMultiplier = (float) resolution / FoliageManager.Foliage_Instance_Area_Size;
            int size = (prototype.FadeDistance + Generation_Safe_Distance) * 2;
            List<FoliageMeshData> lodsData = prototype.GetLODsData();

            byte currentDensity;

            int densityCount = densities.Count;

            List<byte> densitiesToRemove = new List<byte>();

            for (int i = 0; i < densityCount; i++)
            {
                currentDensity = densities[i];

                densities[i] = (byte) Mathf.CeilToInt(currentDensity * resMultiplier);

                currentDensity = densities[i];

                if (densities.FindAll(delegate(byte a)
                {
                    return a == currentDensity;
                }).Count > 1)
                {
                    densitiesToRemove.Add(currentDensity);
                    continue;
                }
            }

            for (int i = 0; i < densitiesToRemove.Count; i++)
            {
                densities.Remove(densitiesToRemove[i]);
            }

            // 计算块大小            
            int realDensity = densities[0];
            int maxCapability = prototype.MaxFoliageCapability / realDensity;
            maxCapability = Mathf.FloorToInt(Mathf.Sqrt(maxCapability));

            float unitsNeeded = (float) size / maxCapability;
            int unitsNeededFloored = Mathf.FloorToInt(unitsNeeded);
            int unitsNeededCeiled = Mathf.CeilToInt(unitsNeeded);

            int unitCoverage = Mathf.FloorToInt(size / unitsNeeded);
            int remainderCoverage = size - (unitCoverage * unitsNeededFloored);

            Mesh[,,] lodMeshes = new Mesh[lodsData.Count, densities.Count, remainderCoverage > 0 ? 2 : 1];
            FoliageMeshData meshData;

            for (int lodMeshesIndex = 0; lodMeshesIndex < lodsData.Count; lodMeshesIndex++)
            {
                meshData = lodsData[lodMeshesIndex];

                lodMeshes = CreateMeshes(meshData, prototype, lodMeshes, lodMeshesIndex, 0, densities, unitCoverage);

                if (remainderCoverage > 0)
                {
                    lodMeshes = CreateMeshes(meshData, prototype, lodMeshes, lodMeshesIndex, 1, densities, remainderCoverage);
                }
            }

            Vector3 instancePosition = new Vector3();
            Vector3 instancesCenter = new Vector3(size / 2f, 0, size / 2f);

            for (int x = 0; x < unitsNeededCeiled; x++)
            {
                instancePosition.x = x * unitCoverage;

                for (int z = 0; z < unitsNeededCeiled; z++)
                {
                    instancePosition.z = z * unitCoverage;

                    meshGroup.Add(CreateFoliageMeshInstance(prototype, instancePosition, x > unitsNeededFloored || z > unitsNeededFloored ? 1 : 0));
                }
            }

            meshGroup.Finish();

            Mesh currentMesh;
            bool hasRemainder;
            int meshesCount;

            for (int lodsIndex = 0; lodsIndex < lodsData.Count; lodsIndex++)
            {
                hasRemainder = remainderCoverage > 0;
                meshesCount = hasRemainder ? 2 : 1;

                for (int count = 0; count < meshesCount; count++)
                {
                    for (int lodIndex = 0; lodIndex < densities.Count; lodIndex++)
                    {
                        currentDensity = densities[lodIndex];

                        currentMesh = lodMeshes[lodsIndex, lodIndex, count];

                        for (int i = 0; i < meshGroup.Count; i++)
                        {
                            FoliageMeshInstance mInstance = meshGroup.MeshInstanceArray[i];

                            if (mInstance.MeshIndex == count)
                            {
                                mInstance.UpdateMeshBounds(currentMesh, instancesCenter);
                            }
                        }

                        currentMesh.name = string.Format("Foliage Mesh ({0}) ({1})", currentDensity, currentMesh.vertexCount);
                        currentMesh.bounds = FoliageManager.FoliageMainAreaBounds;
                    }
                }
            }

            return lodMeshes;
        }

        /// <summary>
        /// Create meshes for a certain coverage.
        /// </summary>
        /// <param name="prototype"></param>
        /// <param name="densities"></param>
        /// <param name="coverage"></param>
        /// <param name="reminder"></param>
        /// <returns></returns>
        private static Mesh[,,] CreateMeshes(FoliageMeshData meshData, FoliagePrototype prototype, Mesh[,,] meshes, int lodMeshIndex, int meshIndex, List<byte> densities,
                                             int coverageUnits)
        {
            int density;
            for (int i = 0; i < densities.Count; i++)
            {
                density = densities[i];

                Mesh mesh = FillMesh(meshData, prototype, density, coverageUnits);
                meshes[lodMeshIndex, i, meshIndex] = mesh;
            }

            return meshes;
        }

        /// <summary>
        /// 填充一个Mesh信息
        /// </summary>
        /// <param name="prototype"></param>
        /// <param name="density"></param>
        /// <param name="units"></param>
        /// <returns></returns>
        private static Mesh FillMesh(FoliageMeshData meshData, FoliagePrototype prototype, int density, int units)
        {
            Mesh mesh = FoliageMeshUtility.CreateNewMesh();
            Vector3 position = new Vector3();

            ReusableCombineInstances.Clear();

            Matrix4x4 matrix;

            Mesh prototypeMesh = prototype.FoliageMesh.Mesh;
            float spread = prototype.Spread;
            int prototypeId = prototype.Id;

            for (int x = 0; x < units; x++)
            {
                for (int z = 0; z < units; z++)
                {
                    position.x = x;
                    position.z = z;

                    matrix = Matrix4x4.TRS(position, GenerationOptimizationPreGeneratedQuaternionIdentity, Vector3.one);

                    for (int densityIndex = 1; densityIndex <= density; densityIndex++)
                    {
                        ReusableCombineInstances.Add(new UNCombineInstance(matrix, prototypeMesh, spread, densityIndex, prototypeId, x, z));
                    }
                }
            }

            FoliageMeshUtility.CombineMeshes(ReusableCombineInstances, mesh, meshData);

            return mesh;
        }

        /// <summary>
        /// 获取一个Map实例的密度阈值信息
        /// </summary>
        /// <param name="pos"></param>
        /// <returns></returns>
        public byte GetDensity(float positionX, float positionZ, byte maxDensity, FoliageDensityMap map, int mapWidth)
        {
            byte density = 0;

            int posX = FoliageMathUtility.iClamp((int) positionX - 1, 0, mapWidth);
            int posZ = FoliageMathUtility.iClamp((int) positionZ - 1, 0, mapWidth);

            int targetX = FoliageMathUtility.iClampMax(posX + BoundsSizeX + 2, mapWidth);
            int targetZ = FoliageMathUtility.iClampMax(posZ + BoundsSizeZ + 2, mapWidth);

            for (int x = posX; x < targetX; x++)
            {
                for (int z = posZ; z < targetZ; z++)
                {
                    byte currentDensity = map.GetPrototypeDensity(x, z, Prototype.Id);

                    if (currentDensity >= maxDensity)
                    {
                        return maxDensity;
                    }

                    if (currentDensity > density)
                    {
                        density = currentDensity;
                    }
                }
            }

            return density;
        }

        /// <summary>
        /// 更新Mesh的AABB
        /// </summary>
        /// <param name="mesh"></param>
        internal void UpdateMeshBounds(Mesh mesh, Vector3 instancesCenter)
        {
            if (mesh == null || BoundsSizeX == 0 || BoundsSizeZ == 0)
            {
                CullBounds.size = new Vector3(mesh.bounds.size.x, 100, mesh.bounds.size.z);
                BoundsSizeX = Mathf.CeilToInt(mesh.bounds.size.x);
                BoundsSizeZ = Mathf.CeilToInt(mesh.bounds.size.z);
                BoundsExtents = CullBounds.extents;

                Center = Position + BoundsExtents;
                Center.y = 0;

                DistanceToCenter = Vector3.Distance(instancesCenter, Center);

                float radius = Vector3.Distance(Center, Position);

                DistanceToCenter -= radius;
            }
        }

        /// <summary>
        /// 检查点师傅在视野范围
        /// </summary>
        /// <param name="cameraPlanes"></param>
        /// <param name="pos"></param>
        /// <param name="height"></param>
        /// <returns></returns>
        internal bool CheckViewPort(Plane[] cameraPlanes, Vector3 pos, float height)
        {
            pos.x += BoundsExtents.x;
            pos.y = height;
            pos.z += BoundsExtents.z;

            CullBounds.center = pos;

            return GeometryUtility.TestPlanesAABB(cameraPlanes, CullBounds);
        }

        /// <summary>
        /// 销毁一个Mesh实例
        /// </summary>
        internal void Destroy()
        {
            Prototype = null;
            System.GC.SuppressFinalize(this);
        }

        /// <summary>
        /// 绘制Mesh实例
        /// </summary>
        /// <param name="pos"></param>
        /// <param name="mesh"></param>
        /// <param name="mat"></param>
        /// <param name="camera"></param>
        /// <param name="cameraPos"></param>
        /// <param name="prototype"></param>
        /// <param name="matBlock"></param>
        /// <param name="useQualitySettingsShadows"></param>
        /// <param name="shadowDistance"></param>
        internal void DrawAndUpdate(Vector3 pos, Mesh mesh, Camera camera, Vector3 cameraPos, MaterialPropertyBlock matBlock, bool useQualitySettingsShadows, float shadowDistance)
        {
            if (Prototype == null)
            {
                return;
            }

            ShadowCastingMode castMode = Prototype.CastShadows && (camera == null || (useQualitySettingsShadows || Vector3.Distance(pos, cameraPos) < shadowDistance)) ?
                    ShadowCastingMode.On : ShadowCastingMode.Off;

            Graphics.DrawMesh(mesh, GenerationOptimizationPreGeneratedVector3Zero, GenerationOptimizationPreGeneratedQuaternionIdentity, Prototype.FoliageMesh.Mat,
                    Prototype.RenderingLayer, camera, 0, matBlock, castMode, Prototype.ReceiveShadows, null);
        }
    }
}
